package com.ouyunc.common.utils;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.ouyunc.common.base.Request;
import com.ouyunc.common.constant.CommonConstant;
import com.ouyunc.common.context.ThreadLocalContextHolder;
import okhttp3.*;
import org.apache.commons.collections4.MapUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.util.UriComponentsBuilder;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.IOException;
import java.net.URL;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * okhttp 请求工具类
 */
public enum OkHttp3ClientUtil {
    INSTANCE;

    private static final Logger logger = LoggerFactory.getLogger(ApacheHttpClientUtil.class);

    // json
    private static final MediaType JSON_MEDIA_TYPE = MediaType.Companion.get("application/json; charset=utf-8");
    // 表单,暂时不做支持
    private static final MediaType FROM_MEDIA_TYPE = MediaType.Companion.get("application/x-www-form-urlencoded");


    // 默认建立连接的超时10s
    private static final int DEFAULT_CONNECT_TIMEOUT = 10;

    // 默认获取数据的超时时间10s
    private static final int DEFAULT_READ_TIMEOUT = 10;

    // 默认写数据的超时时间10s
    private static final int DEFAULT_WRITE_TIMEOUT = 30;



    // 跳过https
    private static OkHttpClient getHttpClient() {
        try {
            SSLContext sslContext = SSLContext.getInstance("SSL");
            X509TrustManager tm = new X509TrustManager() {
                @Override
                public void checkClientTrusted(X509Certificate[] chain, String authType) {
                }

                @Override
                public void checkServerTrusted(X509Certificate[] chain, String authType) {
                }

                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[]{};
                }
            };
            sslContext.init(null, new TrustManager[]{tm}, new SecureRandom());
            HostnameVerifier hostnameVerifier = (s, sslSession) -> true;
            return new OkHttpClient.Builder()
                    .connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)
                    .readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
                    .writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.SECONDS)
                    .retryOnConnectionFailure(true) //是否自动重连
                    // https 配置
                    .sslSocketFactory(sslContext.getSocketFactory(), tm)
                    .hostnameVerifier(hostnameVerifier)
                    // 使用连接池
                    .connectionPool(new ConnectionPool(50, 1, TimeUnit.NANOSECONDS)) // 不缓存连接，其实后面两个参数就无所谓了
                    .build();
        } catch (Exception e) {
            logger.error("okHttps client 初始化失败！开始okhttp client 初始化...");
            // 使用http 请求
            return new OkHttpClient.Builder()
                    .connectTimeout(DEFAULT_CONNECT_TIMEOUT, TimeUnit.SECONDS)
                    .readTimeout(DEFAULT_READ_TIMEOUT, TimeUnit.SECONDS)
                    .writeTimeout(DEFAULT_WRITE_TIMEOUT, TimeUnit.SECONDS)
                    .retryOnConnectionFailure(true)            //是否自动重连
                    // 使用连接池
                    .connectionPool(new ConnectionPool(50, 1, TimeUnit.NANOSECONDS)) // 不缓存连接，其实后面两个参数就无所谓了
                    .build();
        }
    }


    public Response doGet(Request request) {
        okhttp3.Request okRequest = new okhttp3.Request.Builder()
                .get()
                .url(assembleURL(request))
                .headers(assembleHeaders(request))
                .build();
        return common(okRequest);
    }

    /**
     * json 格式提交 mime 类型：application/json; charset=utf-8
     */
    public Response doPost(Request request) {
        okhttp3.Request okRequest = new okhttp3.Request.Builder()
                .post(RequestBody.Companion.create(JSONObject.toJSONString(request.getBody(), SerializerFeature.WriteMapNullValue), JSON_MEDIA_TYPE))
                .url(assembleURL(request))
                .headers(assembleHeaders(request))
                .build();
        return common(okRequest);
    }



    public Response doPut(Request request) {
        okhttp3.Request okRequest = new okhttp3.Request.Builder()
                .put(RequestBody.Companion.create(JSONObject.toJSONString(request.getBody(), SerializerFeature.WriteMapNullValue), JSON_MEDIA_TYPE))
                .url(assembleURL(request))
                .headers(assembleHeaders(request))
                .build();
        return common(okRequest);
    }

    public Response doDelete(Request request) {
        okhttp3.Request okRequest = new okhttp3.Request.Builder()
                .delete()
                .url(assembleURL(request))
                .headers(assembleHeaders(request))
                .build();
        return common(okRequest);
    }

    public Response doUpload(Request request) {
        MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        if (MapUtils.isNotEmpty(request.getFiles())) {
            request.getFiles().forEach((argName, fileList)->{
                fileList.forEach(file -> {
                    try {
                        builder.addFormDataPart(argName, file.getOriginalFilename(), MultipartBody.create(file.getBytes()));
                    } catch (IOException e) {
                        logger.error("okHttpClient 封装文件上传body 失败！");
                        e.printStackTrace();
                    }
                });
            });
        }
        okhttp3.Request okRequest = new okhttp3.Request.Builder()
                .post(builder.build())
                .url(assembleURL(request))
                .headers(assembleHeaders(request))
                .build();
        return common(okRequest);
    }



    /**
     * 组装请求头
     * @param request
     * @return
     */
    private Headers assembleHeaders(Request request) {
        Headers.Builder builder = new Headers.Builder();
        // 设置默认的请求头
        Map<String, String> currentRequestHeaders = ThreadLocalContextHolder.get(CommonConstant.CURRENT_REQUEST_HEADERS);
        if (MapUtils.isNotEmpty(currentRequestHeaders)) {
            // @TODO  这里根据需要可以从当前线程上线文中获取请求头或请求参数信息
            //currentRequestHeaders.forEach((key,value) -> builder.add(key, value));
        }
        // 设置自定义请求头
        Map<String, String> customHeader = request.getHeader();
        if (MapUtils.isNotEmpty(customHeader)) {
            customHeader.forEach((key,value) -> builder.add(key, value));
        }
        return builder.build();
    }


    /**
     * 组装url,获取url 进行参数拼接
     * @return
     */
    private URL assembleURL(Request request) {
        try {
            UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(request.getUrl());
            if (MapUtils.isNotEmpty(request.getParam())) {
                request.getParam().forEach((key,value) -> uriBuilder.queryParam(key, value));
            }
            return uriBuilder.encode().build().toUri().toURL();
        } catch (Exception e) {
            logger.error("组装URL 异常：{}", e.getMessage());
            e.printStackTrace();
            throw new RuntimeException("组装URL 异常!");
        }
    }

    // 公共方法执行
    private Response common(okhttp3.Request okRequest) {
        try {
            Call call = getHttpClient().newCall(okRequest);
            return call.execute();
        } catch (Exception e) {
            logger.error("okHttpClient 请求失败！{}", e.getMessage());
            e.printStackTrace();
            throw new RuntimeException("okHttpClient 请求失败");
        }
    }

}
